package dongshi.daddy.byzantine.kouxin;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/*
一个叛徒的场景，此时假定一共有4个将军ABCD，其中B是叛徒，则达成共识过程如下，
首先看步骤1发送消息的是忠臣：
    1：假定A首先发送进攻消息（注意程序实现这里发出去的消息时随机的，模拟各种场景验证算法）给BCD
    2：BCD在收到A的进攻消息后，忠臣CD会发送进攻消息给A外的其他的两个将军，但B是叛徒，其会胡乱发送消息给A外的其他两位将军
其次看步骤1发送消息的是叛徒：
    1：B发送随机的进攻和撤退消息给ACD
    2：ACD收到消息后，将消息分别转发给B之外的其他2个将军
 */
// 注意：简单起见，本程序模拟A首先发
public class OneTraitor {
    private static String IP = "127.0.0.1";
    private static int A_PORT = 6666;
    private static int B_PORT = 6667;
    private static int C_PORT = 6668;
    private static int D_PORT = 6669;
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    // 进攻消息
    private static String MESSAGE_ATTACK = "attack";
    // 撤退消息
    private static String MESSAGE_RETREAT = "retreat";
    private static String usedMsg = MESSAGE_ATTACK;
    // 第一个发送消息的将军，用于判断第二轮是否是需要发送消息
    private static String FIRST_SENDER = "";
    // 保证第一轮消息全部收到
    private static CountDownLatch firstRoundReceive = new CountDownLatch(3);

    public static void main(String[] args) throws Exception {
        // 模拟4个socket程序是socketA，socketB，socketC，socketD作为四位将军
        Thread threadA = new Thread(() -> {
            // 10秒后决定进攻还是撤退
            try {
                Thread.sleep(10000);
                System.out.println("A: " + usedMsg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();

        Thread threadB = new Thread(() -> {
            try {
                ServerSocket serverB = new ServerSocket(B_PORT);
                System.out.println("B(叛徒)准备接收消息");
                String firstReceive = firstReceviveMsg(serverB);
                System.out.println("B fist: " + firstReceive);
                firstRoundReceive.countDown();
                firstRoundReceive.await();
                // B是叛徒，不管B接收消息，只需要给CD发送随机消息（不管第一次收到的消息时什么），这里模拟给C发送进攻消息，给D发送撤退消息
                Thread.sleep(4000);
                System.out.println("叛徒B开始发的二轮消息");
//                sendMsg(MESSAGE_ATTACK, C_PORT);
                sendMsg(MESSAGE_RETREAT, C_PORT);
                sendMsg(MESSAGE_RETREAT, D_PORT);
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
        threadB.start();

        Thread threadC = new Thread(() -> {
            try {
                ServerSocket serverC = new ServerSocket(C_PORT);
                System.out.println("C准备接收消息");
                String firstReceive = firstReceviveMsg(serverC);
                System.out.println("C fist: " + firstReceive);
                firstRoundReceive.countDown();
                firstRoundReceive.await();

                // 准备接收第二轮消息
                List<String> messageList = new ArrayList<>();
                System.out.println("C开始接收第二轮消息");
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (messageList.size() < 2) {
                            Socket accept = null;
                            try {
                                accept = serverC.accept();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            // 异步处理
                            Socket finalAccept = accept;
                            new Thread(() -> {
                                String c = roundTwoMsg(finalAccept);
                                messageList.add(c);
                                System.out.println("C 第二轮收到：" + c + " , " + messageList.size());
                            }).start();
                        }

                    }
                }).start();
                // 等一会开始发送第二轮消息，是忠臣所以发送的是第一轮收到的消息
                Thread.sleep(5000);
                System.out.println("C开始发送第二轮消息");
                sendMsg(firstReceive, B_PORT);
                sendMsg(firstReceive, D_PORT);
                // 10秒后决定进攻还是撤退
                Thread.sleep(10000);
                messageList.add(firstReceive);
                System.out.println("C:" + messageList);

            } catch (Exception e) {
            }

        });
        threadC.start();

        Thread threadD = new Thread(() -> {
            try {
                ServerSocket serverD = new ServerSocket(D_PORT);
                System.out.println("D准备接收消息");
                String firstReceive = firstReceviveMsg(serverD);
                System.out.println("D fist: " + firstReceive);
                firstRoundReceive.countDown();
                firstRoundReceive.await();

                // 准备接收第二轮消息
                List<String> messageList = new ArrayList<>();
                System.out.println("D开始接收第二轮消息");
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (messageList.size() < 2) {
                            Socket accept = null;
                            try {
                                accept = serverD.accept();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            // 异步处理
                            Socket finalAccept = accept;
                            new Thread(() -> {
                                String c = roundTwoMsg(finalAccept);
                                messageList.add(c);
                                System.out.println("D 第二轮收到：" + c + " , " + messageList.size());
                            }).start();
                        }

                    }
                }).start();
                // 等一会开始发送第二轮消息，是忠臣所以发送的是第一轮收到的消息
                Thread.sleep(6000);
                System.out.println("D开始发送第二轮消息");
                sendMsg(firstReceive, B_PORT);
                sendMsg(firstReceive, C_PORT);
                // 10秒后决定进攻还是撤退
                Thread.sleep(10000);
                messageList.add(firstReceive);
                System.out.println("D:" + messageList);
            } catch (Exception e) {
            }
        });
        threadD.start();

        // 现在将军们都能接收消息了，开始第一轮
        // 情况1：忠诚的将军先送发送消息，这里是撤退消息还是进攻消息随机
        honestFirstSend();

        // 避免主线程退出
        countDownLatch.await();
    }

    private static String roundTwoMsg(/*String whoAmI, */Socket accept) {
        try {
            InputStream is = accept.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            while (true) {
                try {
                    String s = br.readLine();
                    if (s == null) {
                        break;
                        // 模拟第二轮发送消息，这里发送的是第一轮收到的消息
                    }
                    return s;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {}
        return "";
    }

    private static String firstReceviveMsg(ServerSocket serverA/*, String show*/) throws IOException {
        Socket socketA = serverA.accept();
        InputStream is = socketA.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        while (true) {
            try {
                String s = br.readLine();
                if (s == null) {
                    break;
                    // 模拟第二轮发送消息，这里发送的是第一轮收到的消息
                }
                return s;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    private static void honestFirstSend() {
        try {
            FIRST_SENDER = "A";
            // 给将军们准备时间，防止收不到消息
            Thread.sleep(1000);
            int i = (int) (Math.random() * 50 + 50);

            String sendMsg = usedMsg;
            System.out.println("A第一次发送的消息是：" + sendMsg);
            sendMsg(sendMsg, B_PORT);
            sendMsg(sendMsg, C_PORT);
            sendMsg(sendMsg, D_PORT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sendMsg(String sendMsg, int port) throws IOException {
        Socket socket = new Socket("127.0.0.1", port);
        OutputStream os = socket.getOutputStream();
        PrintWriter pw = new PrintWriter(os);
        pw.println(sendMsg);
        pw.flush();
        socket.close();
    }
}
